#ifndef frame_h
#define frame_h

#include "libavcodec/avcodec.h"
#include "libavutil/common.h"
#include "packet.c"

#define VIDEO_PICTURE_QUEUE_SIZE 3
#define SUBPICTURE_QUEUE_SIZE 16
#define SAMPLE_QUEUE_SIZE 9
#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))

/* Common struct for handling all types of decoded data and allocated render buffers. */
typedef struct Frame
{
  AVFrame*   frame;
  AVSubtitle sub;
  int        serial;
  double     pts;      /* presentation timestamp for the frame */
  double     duration; /* estimated duration of the frame */
  int64_t    pos;      /* byte position of the frame in the input file */
  int        width;
  int        height;
  int        format;
  AVRational sar;
  int        uploaded;
  int        flip_v;
} Frame;

typedef struct FrameQueue
{
  Frame        queue[FRAME_QUEUE_SIZE];
  int          rindex;
  int          windex;
  int          size;
  int          max_size;
  int          keep_last;
  int          rindex_shown;
  SDL_mutex*   mutex;
  SDL_cond*    cond;
  PacketQueue* pktq;
} FrameQueue;

void    frame_queue_unref_item(Frame* vp);
int     frame_queue_init(FrameQueue* f, PacketQueue* pktq, int max_size, int keep_last);
void    frame_queue_destory(FrameQueue* f);
void    frame_queue_signal(FrameQueue* f);
Frame*  frame_queue_peek(FrameQueue* f);
Frame*  frame_queue_peek_next(FrameQueue* f);
Frame*  frame_queue_peek_last(FrameQueue* f);
Frame*  frame_queue_peek_writable(FrameQueue* f);
Frame*  frame_queue_peek_readable(FrameQueue* f);
void    frame_queue_push(FrameQueue* f);
void    frame_queue_next(FrameQueue* f);
int     frame_queue_nb_remaining(FrameQueue* f);
int64_t frame_queue_last_pos(FrameQueue* f);

#endif

#if __INCLUDE_LEVEL__ == 0

void frame_queue_unref_item(Frame* vp)
{
  av_frame_unref(vp->frame);
  avsubtitle_free(&vp->sub);
}

int frame_queue_init(FrameQueue* f, PacketQueue* pktq, int max_size, int keep_last)
{
  int i;
  memset(f, 0, sizeof(FrameQueue));
  if (!(f->mutex = SDL_CreateMutex()))
  {
    av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
    return AVERROR(ENOMEM);
  }
  if (!(f->cond = SDL_CreateCond()))
  {
    av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
    return AVERROR(ENOMEM);
  }
  f->pktq      = pktq;
  f->max_size  = FFMIN(max_size, FRAME_QUEUE_SIZE);
  f->keep_last = !!keep_last;
  for (i = 0; i < f->max_size; i++)
    if (!(f->queue[i].frame = av_frame_alloc()))
      return AVERROR(ENOMEM);
  return 0;
}

void frame_queue_destory(FrameQueue* f)
{
  int i;
  for (i = 0; i < f->max_size; i++)
  {
    Frame* vp = &f->queue[i];
    frame_queue_unref_item(vp);
    av_frame_free(&vp->frame);
  }
  SDL_DestroyMutex(f->mutex);
  SDL_DestroyCond(f->cond);
}

void frame_queue_signal(FrameQueue* f)
{
  SDL_LockMutex(f->mutex);
  SDL_CondSignal(f->cond);
  SDL_UnlockMutex(f->mutex);
}

Frame* frame_queue_peek(FrameQueue* f)
{
  return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
}

Frame* frame_queue_peek_next(FrameQueue* f)
{
  return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size];
}

Frame* frame_queue_peek_last(FrameQueue* f)
{
  return &f->queue[f->rindex];
}

Frame* frame_queue_peek_writable(FrameQueue* f)
{
  /* wait until we have space to put a new frame */
  SDL_LockMutex(f->mutex);
  while (f->size >= f->max_size &&
         !f->pktq->abort_request)
  {
    SDL_CondWait(f->cond, f->mutex);
  }
  SDL_UnlockMutex(f->mutex);

  if (f->pktq->abort_request)
    return NULL;

  return &f->queue[f->windex];
}

Frame* frame_queue_peek_readable(FrameQueue* f)
{
  /* wait until we have a readable a new frame */
  SDL_LockMutex(f->mutex);
  while (f->size - f->rindex_shown <= 0 &&
         !f->pktq->abort_request)
  {
    SDL_CondWait(f->cond, f->mutex);
  }
  SDL_UnlockMutex(f->mutex);

  if (f->pktq->abort_request)
    return NULL;

  return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
}

void frame_queue_push(FrameQueue* f)
{
  if (++f->windex == f->max_size)
    f->windex = 0;
  SDL_LockMutex(f->mutex);
  f->size++;
  SDL_CondSignal(f->cond);
  SDL_UnlockMutex(f->mutex);
}

void frame_queue_next(FrameQueue* f)
{
  if (f->keep_last && !f->rindex_shown)
  {
    f->rindex_shown = 1;
    return;
  }
  frame_queue_unref_item(&f->queue[f->rindex]);
  if (++f->rindex == f->max_size)
    f->rindex = 0;
  SDL_LockMutex(f->mutex);
  f->size--;
  SDL_CondSignal(f->cond);
  SDL_UnlockMutex(f->mutex);
}

/* return the number of undisplayed frames in the queue */
int frame_queue_nb_remaining(FrameQueue* f)
{
  return f->size - f->rindex_shown;
}

/* return last shown position */
int64_t frame_queue_last_pos(FrameQueue* f)
{
  Frame* fp = &f->queue[f->rindex];
  if (f->rindex_shown && fp->serial == f->pktq->serial)
    return fp->pos;
  else
    return -1;
}

#endif
